package edu.zju.udms.dnavigator.core.repository;

import java.io.File;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Observable;
import java.util.Observer;

import org.apache.log4j.Logger;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.index.IndexWriterConfig.OpenMode;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.Sort;
import org.apache.lucene.search.SortField;
import org.apache.lucene.search.TopDocs;

import edu.zju.udms.dnavigator.core.config.Configuration;
import edu.zju.udms.dnavigator.core.entity.Entity;
import edu.zju.udms.dnavigator.core.entity.MetaLuceneMapper;
import edu.zju.udms.dnavigator.core.entity.MetaObject;
import edu.zju.udms.dnavigator.core.entity.Record;
import edu.zju.udms.dnavigator.core.entity.Relation;
import edu.zju.udms.dnavigator.core.repository.exception.IndexDirectoryIllegalException;

public class LuceneRepository implements Repository,Observer{
	private static final Logger logger=Logger.getLogger(LuceneRepository.class);
	
	private Configuration configuration;
	private LuceneDataset entities, relations, records;
	private Sort versionSort = new Sort(new SortField("version",SortField.Type.LONG, true));
	private MetaLuceneMapper mlp = MetaLuceneMapper.getInstance();

	public LuceneRepository(Configuration configuration) throws Exception{
		this.configuration = configuration;
		((Observable)configuration).addObserver(this);
		flushConfiguration();
	}
	
	private void flushConfiguration() throws Exception{
		String indexDir;
		File rootDir,entitiesDir, relationsDir, recordsDir;
		double bufferSize = 0;
		if(entities!=null){
			entities.finalize();
		}
		if(relations!=null){
			relations.finalize();
		}
		indexDir = configuration.getProperty("index.root.dir");
		logger.info("Index root directory:"+indexDir);
		rootDir = new File(indexDir);
		if(rootDir.exists()&&!rootDir.isDirectory()){
			throw new IndexDirectoryIllegalException("index root "+rootDir.getCanonicalPath()+" must be a directory");
		}

		entitiesDir = new File(new URI(rootDir.toURI()+"/entities"));
		relationsDir = new File(new URI(rootDir.toURI()+"/relations"));
		recordsDir = new File(new URI(rootDir.toURI()+"/records"));	
		if(entitiesDir.exists() && !entitiesDir.isDirectory()){
			throw new IndexDirectoryIllegalException("entities directory "+entitiesDir.getCanonicalPath()+" must be a directory");
		}
		if(relationsDir.exists() && !relationsDir.isDirectory()){
			throw new IndexDirectoryIllegalException("relations directory "+relationsDir.getCanonicalPath()+" must be a directory");
		}
		if(recordsDir.exists() && !recordsDir.isDirectory()){
			throw new IndexDirectoryIllegalException("records directory "+recordsDir.getCanonicalPath()+" must be a directory");
		}
		entitiesDir.mkdirs();
		relationsDir.mkdirs();
		recordsDir.mkdirs();
		
		if (Boolean.parseBoolean(configuration.getProperty("index.use.buffer"))){
			bufferSize = Double.parseDouble(configuration.getProperty("index.buffer.size"));
			logger.info(String.format("Use RAM Buffer ,total size %f : MB",bufferSize));
		}
		boolean forceMerge = Boolean.parseBoolean(configuration.getProperty("index.force.merge"));
		int maxNumSegments = Integer.parseInt(configuration.getProperty("index.max.num.segments"));
		entities = new LuceneDataset(entitiesDir.toURI(), OpenMode.CREATE_OR_APPEND, bufferSize*0.6, "name", new StandardAnalyzer(), versionSort,forceMerge,maxNumSegments);
		relations = new LuceneDataset(relationsDir.toURI(), OpenMode.CREATE_OR_APPEND, bufferSize*0.2, "label", new StandardAnalyzer(), versionSort,forceMerge,maxNumSegments);
		records = new LuceneDataset(recordsDir.toURI(), OpenMode.CREATE_OR_APPEND, bufferSize*0.2, "sourceKey", new StandardAnalyzer(), versionSort,forceMerge,maxNumSegments);
	}
	
	@Override
	public void update(Observable o, Object arg) {
		// TODO Auto-generated method stub
		
	}
	
	
	@Override
	public <T extends MetaObject> List<T> filter(Class<T> clazz, String field, Object value,
			int size) {
		Map<String, Object> conditions=new HashMap<>();
		conditions.put(field, value);
		return filter(clazz,conditions,size);
	}

	@SuppressWarnings("unchecked")
	@Override
	public <T extends MetaObject> List<T> filter(Class<T> clazz, Map<String, Object> conditions,
			int size) {
		Map<String, Object> new_map=new HashMap<>();
		new_map.putAll(conditions);
		new_map.put("_classname", clazz.getCanonicalName());
		Query query = mlp.generateQuery(conditions);
		TopDocs topDocs = entities.search(query, size);
		List<T> result = (List<T>) parseTopDocs(topDocs, entities);
		return result;
	}

	@SuppressWarnings("unchecked")
	@Override
	public List<Relation> filter(Entity from, Entity to, String label, int size) {
		Map<String, Object> conditions=new HashMap<>();
		conditions.put("fromId", from.getId().toString());
		conditions.put("toId", to.getId().toString());
		conditions.put("label", label);
		Query query = mlp.generateQuery(conditions);
		TopDocs topDocs = relations.search(query, size);
		List<Relation> result = (List<Relation>) parseTopDocs(topDocs, relations);
		return result;
	}
	
	@SuppressWarnings("unchecked")
	@Override
	public List<Record> filterRecords(Map<String, Object> conditions, int size) {
		Query query = mlp.generateQuery(conditions);
		return (List<Record>) records.parseDocsToMeta(records.search(query, size));
	}
	
	@Override
	public List<Map<String, String>> filterRecords2Map(Map<String, Object> conditions, int size) {
		Query query = mlp.generateQuery(conditions);
		return records.parseDocsToMap(records.search(query, size));
	}

	@Override
	public <T extends MetaObject> T filterLast(Class<T> clazz, String field, Object value) {
		List<T> res = filter(clazz,field,value,1);
		return res.size()>0?(T)res.get(0):null;
	}

	@Override
	public <T extends MetaObject> T filterLast(Class<T> clazz, Map<String, Object> conditions) {
		List<T> res = filter(clazz,conditions,1);
		return res.size()>0?(T)res.get(0):null;
	}

	@Override
	public String filterLastID(Class<?> clazz, String field, Object value) {
		Map<String, Object> conditions=new HashMap<>();
		conditions.put(field, value);
		return filterLastID(clazz,conditions);
	}

	@Override
	public String filterLastID(Class<?> clazz, Map<String, Object> conditions) {
		Map<String, Object> new_map=new HashMap<>();
		new_map.putAll(conditions);
		new_map.put("_classname", clazz.getCanonicalName());
		Query query = mlp.generateQuery(conditions);
		TopDocs topDocs = entities.search(query, 1);
		if(topDocs.totalHits>0){
			Document doc = entities.getDoc(topDocs.scoreDocs[0].doc);
			return doc.get("_id");
		}else
			return null;
	}

	public List<Entity> search(String query, int size) {
		logger.info(String.format("search method: %s ,required size %d",query,size));
		return _search(query, size);
	}
	
	@SuppressWarnings("unchecked")
	public List<Entity> _search(String query, int size) {
		List<? extends MetaObject> result;
		TopDocs topDocs;
		topDocs = entities.search(query, size);
		result = parseTopDocs(topDocs, entities);
		return (List<Entity>) result;
	}
	
	public List<Map<String, String>> searchMap(Query query, int size) {
		return _searchMap(query,size);
	}
	
	public List<Map<String, String>> _searchMap(Query query, int size) {
		TopDocs topDocs;
		topDocs = entities.search(query, size);
		List<Map<String, String>> result = entities.parseDocsToMap(topDocs);
		return result;
	}
	
	@SuppressWarnings("unchecked")
	public List<Entity> search(Query query, int size) {
		logger.info(String.format("search method: %s ,required size %d",query,size));
		return (List<Entity>) _search(entities,query, size);
	}
	
	private List<? extends MetaObject> _search(LuceneDataset dataset,Query query, int size) {
		TopDocs topDocs;
		topDocs = dataset.search(query, size);
		return dataset.parseDocsToMeta(topDocs);
	}
	
	private void persist(MetaObject meta) throws Exception {
		Class<? extends MetaObject> clazz = meta.getClass();
		LuceneDataset dataset;
		if(Entity.class.isAssignableFrom(clazz)){
			dataset = entities;
		}else{
			dataset = relations;
		}
		Query query = mlp.meta2query(meta);
		TopDocs topDocs=dataset.search(query, 1);
		if(topDocs.totalHits>0){
			List<? extends MetaObject> res = parseTopDocs(topDocs, dataset);
			MetaObject old_meta = res.get(0);
			if(mlp.isIdentical(meta, old_meta)&&mlp.isVersioned(meta, old_meta)){
				dataset.delete(mlp.id2query(old_meta.getId()));
			}
		}
		Document doc = mlp.meta2doc(meta);
		dataset.add(doc);
	}

	@Override
	public synchronized void persist(Collection<? extends MetaObject> metalist) throws Exception {
		for(MetaObject m : metalist ){
			persist(m);
		}
		entities.commit();
		relations.commit();
		records.commit();
	}
	
	@Deprecated
	/** TODO 删了，用LuceneDataset相应接口*/
	private List<? extends MetaObject> parseTopDocs(TopDocs topDocs,LuceneDataset dataset){
		List<MetaObject> result=new ArrayList<>();
		ScoreDoc[] docs = topDocs.scoreDocs;
		for(ScoreDoc doc : docs){
			result.add(mlp.doc2meta(dataset.getDoc(doc.doc)));
		}
		return result;
	}

	private void _create(LuceneDataset dataset, Collection<? extends MetaObject> metalist) throws Exception {
		for(MetaObject meta:metalist){
			Document doc = mlp.meta2doc(meta);
			dataset.add(doc);
		}
		dataset.commit();
	}
	
	@Override
	public synchronized void truncreateEntities(Collection<Entity> entityList) throws Exception {
		entities.deleteAll();
		_create(entities, entityList);
	}

	@Override
	public synchronized void truncreateRelations(Collection<Relation> relationList) throws Exception {
		relations.deleteAll();
		_create(relations, relationList);
	}

	@Override
	public synchronized void createRelations(Collection<Relation> relationList)
			throws Exception {
		_create(relations, relationList);
	}	
	
	@Override
	public synchronized void updateEntity(Entity entity) throws Exception{
		// TODO Auto-generated method stub
	}

	@SuppressWarnings("unchecked")
	@Override
	public List<Relation> searchRelations(Query query, int size) {
		logger.info(String.format("search method: %s ,required size %d", query, size));
		return (List<Relation>) _search(relations, query, size);
	}
	
	@SuppressWarnings("unchecked")
	@Override
	public List<Record> searchRecords(Query query, int size) {
		logger.info(String.format("search method: %s ,required size %d", query, size));
		return (List<Record>) _search(records, query, size);
	}

	@Override
	public void truncreateRecords(Collection<Record> recordList) throws Exception {
		records.deleteAll();
		_create(records, recordList);		
	}

	@Override
	public void appendRecords(Collection<Record> recordList) throws Exception {
		for (Record record: recordList) {
			Document doc = mlp.meta2doc(record);
			records.add(doc);
		}
		records.commit();
	}
}